package com.googlecode.curvedmetromaps.core.logic.localsearch.geneticalgorithm;

import java.util.Collections;
import java.util.Random;

import com.googlecode.curvedmetromaps.core.logic.costfunction.CostSystem;
import com.googlecode.curvedmetromaps.core.model.MetroEdge;
import com.googlecode.curvedmetromaps.core.model.MetroMap;
import com.googlecode.curvedmetromaps.core.model.MetroStation;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Mutation {

    private static double survivalRate = 0.34;

	private static Random	random	= new Random();

	/**
	 * Mutate all organisms in the population and calculate the
	 * costs of the new organisms.
	 * @param population Population to mutate.
	 */
	public static void mutate(Population population) {
		for (Organism o : population.organisms) {
			Mutation.mutate(o);
		}
	}

	/**
	 * Mutate all organisms in the population in parallel, and calculate the
	 * costs of the new organisms.
	 * @param population Population to mutate.
	 */
	public static void parallelMutate(Population population) {
	    
	    Collections.sort(population.organisms);
	    
	    int startIndex = (int)(survivalRate * population.organisms.size());
	    
	    ExecutorService executor = Executors.newFixedThreadPool(population.organisms.size());
	    
		for (int i = startIndex; i < population.organisms.size(); i++) {
			Organism o = population.organisms.get(i);
			Runnable worker = new MutationWorker(o);
			executor.execute(worker);
		}
	    
		// This will make the executor accept no new threads
	    // and finish all existing threads in the queue
	    executor.shutdown();
	    
	    // Wait until all threads are finish
	    try {
			executor.awaitTermination(1, TimeUnit.HOURS);
		} catch (InterruptedException e) {
			// Failed to perform mutation in time
			e.printStackTrace();
		}
	}

	/**
	 * Mutate a MetroMap
	 * @param o Individual organism which coordinates will be mutated.
	 */
	public static void mutate(Organism o) {
		MetroMap map = o.map;
		float rate = o.mutationRate;

		for (MetroStation m : map.stations) {
			m.x = mutateFloat(m.x, rate);
			m.y = mutateFloat(m.y, rate);
		}

		for (MetroEdge e : map.edgeArray) {
			e.cpX = mutateFloat(e.cpX, rate);
			e.cpY = mutateFloat(e.cpY, rate);
		}

		o.mutationRate = mutateFloat(o.mutationRate, 0.05f * rate);

		// Evaluate cost
		CostSystem costSystem = new CostSystem(o.map);
		o.cost = costSystem.calculateCostSum();
	}

	/**
	 * Mutate a float value.
	 * @param val Current value.
	 * @param rate Rate of mutation determines the range in which the result
	 *            will lie.
	 * @return Mutated value.
	 */
	private static float mutateFloat(float val, float rate) {
		return val + rate * (random.nextFloat() - 0.5f);
	}
	
}
